﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using SIA.Configurators;
using System.Net;
using System.Timers;

namespace SIA.HwAccess.PLCSiemens
{
    public abstract class TCPClientConnection : HwConnection
    {
        private eConnectionStatus _lastStatus = eConnectionStatus.UNDEF;

        protected String _info;
        
        protected Socket _socket;
        protected readonly IPEndPoint _ipe;           
        protected Timer _timer;
        protected Double _interval;
        
        public override event ConnectionStatus StatusChange;
        public override event ConnectionDataEvent DataArrived;

        public override eConnectionStatus Status
        {
            get
            {
                if (_socket == null) 
                    return eConnectionStatus.DISCONNECT;
                if (_socket != null && !_socket.Connected) 
                    return eConnectionStatus.DISCONNECT;
                if (_socket != null && _socket.Connected) 
                    return eConnectionStatus.CONNECT;
                if (_status == eConnectionStatus.ERROR) 
                    return eConnectionStatus.ERROR;
                return eConnectionStatus.UNDEF;
            }
        }

        public TCPClientConnection(IDictionaryProperty connectionProperty, IDictionaryProperty signalsProperty)
        {
            _rawData = new S7RawData();

            this._name = connectionProperty["NOME"].PropertyValue;
            
            if (!connectionProperty.ContainsKey("PORTA")) throw new ArgumentException(this.ToString() + ": Ipossibile trovare la Proprietà: 'PORTA'");
            if (!connectionProperty.ContainsKey("INDIRIZZO")) throw new ArgumentException(this.ToString() + ": Ipossibile trovare la Proprietà: 'INDRIZZO'");
            if (!connectionProperty.ContainsKey("SCANRATE")) throw new ArgumentException(this.ToString() + ": Ipossibile trovare la Proprietà: 'SCANRATE'");

            int portNumber = connectionProperty.ContainsKey("PORTA") ? int.Parse(connectionProperty["PORTA"].PropertyValue) : 2000;
            IPAddress ipAddress = IPAddress.Parse( connectionProperty.ContainsKey("INDIRIZZO") ? connectionProperty["INDIRIZZO"].PropertyValue : "127.0.0.1");
            _ipe = new IPEndPoint(ipAddress, portNumber);

            _interval = connectionProperty.ContainsKey("SCANRATE") ? Double.Parse(connectionProperty["SCANRATE"].PropertyValue) : 1000;


            //Istanzio i Segnali e Popolo la lista
            foreach (IDictionaryProperty dP in signalsProperty.Values)
                if (dP["CONNESSIONE"].PropertyValue == this.Name)
                    switch (dP["TIPO"].PropertyValue.ToUpper())
                    {
                        case "S7BOOLEAN":
                            Signals.Add(dP["NOMESEGNALE"].PropertyValue, new S7Boolean(dP, this));
                            break;
                        case "S7BYTE":
                            Signals.Add(dP["NOMESEGNALE"].PropertyValue, new S7Byte(dP, this));
                            break;
                        case "S7INT":
                            Signals.Add(dP["NOMESEGNALE"].PropertyValue, new S7Int(dP, this));
                            break;
                        case "S7WORD":
                            Signals.Add(dP["NOMESEGNALE"].PropertyValue, new S7Word(dP, this));
                            break;
                        default:
                            SIA.Messages.MessageManager.sendMessage(this, "Segnale Tipo: '" + dP["TIPO"].PropertyValue + "' non supportato", SIA.Messages.eMessageType.WARNING, true);
                            break;
                    }

            //Valuto la presenza di nuovi eventi
            this.evaluateStatusEvent();

            //Inizializzo il Timer
            _timer = new Timer(_interval);
            _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
            _timer.Enabled = true;
        }

        /// <summary>Distruttore</summary>
        ~TCPClientConnection()
        {
            //Eseguo la disconnessione            
            this.disconnect();
        }

        void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                //Se non e' connesso mi connetto
                if (this.Status != eConnectionStatus.CONNECT) this.connect();

                //Se e' connesso eseguo l'update
                if (this.Status == eConnectionStatus.CONNECT) this.update();

                //Verifico Eventi sullo stato
                this.evaluateStatusEvent();

                //Invio l'evento data Arrived
                if (this.Status == eConnectionStatus.CONNECT)
                    if (DataArrived != null) DataArrived(this, _rawData );

                //Se non sono connesso allungo l'intervallo di aggiornamento
                if (this.Status == eConnectionStatus.CONNECT)
                    _timer.Interval = _timer.Interval <= _interval ? _interval : _timer.Interval * 0.9;
                else
                    _timer.Interval = _timer.Interval > (_interval * 10) ? _interval * 10 : _timer.Interval * 2;
            }
            catch (Exception ex)
            {
                _info = "i:" + ex.Message;
                SIA.Messages.MessageManager.sendMessage(this, ex.Message, SIA.Messages.eMessageType.WARNING);
            }
        }

        /// <summary>Evaluate Status Event</summary>
        private void evaluateStatusEvent()
        {
            eConnectionStatus status = Status;

            //Verifico il cambio di stato
            if (_lastStatus != status)
            {
                if (StatusChange != null) StatusChange(this, _lastStatus);
                _lastStatus = status;
            }
        }

        /// <summary>Qui eseguo l'aggiornamento dei dati</summary>
        public abstract void update();        

        /// <summary>Connessione al server(PLC)</summary>
        public override void connect()
        {
            try
            {
                //Se e' gia' connesso esco
                if (_socket != null && _socket.Connected) return;

                //Creo una nuova socket
                _socket = new Socket(_ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                //Tento la Connessione
                _socket.Connect(_ipe);
                
                //Verifico la presenza di eventi
                this.evaluateStatusEvent();
            }
            catch(Exception ex)
            {
                _info = "i:" + ex.Message;
            }
            finally
            {
                //Ablilito il timer
                _timer.Enabled = true;
            }
        }

        /// <summary>Disconnessione dal server(PLC)</summary>
        public override void disconnect()
        {
            try
            {
                //Fermo il timer
                _timer.Enabled = false;
                //Spengo la socket
                _socket.Shutdown(SocketShutdown.Both);
                //Disconnetto la socket
                _socket.Disconnect(false);
                //Distruggo la socket
                _socket = null;
            }
            catch (Exception ex)
            {
                _info = "i:" + ex.Message;
            }
            finally
            {
                //Verifico la presenza di eventi
                this.evaluateStatusEvent();
            }
        }

        /// <summary>Aggiungo informazioni al metodo ToString</summary>
        /// <returns>base.ToString() {info} Interval: {time[ms]}</returns>
        new public String ToString()
        {
            return base.ToString() + " " + _info + " Interval: " + _timer.Interval;
        }
    }
}
